/**
 * @file Board.java
 *   
 * Implements the Board class, which is basically the brains of the game
 */
package core;


import javax.swing.JPanel;

import java.awt.Graphics;
import java.awt.Color;
import java.awt.Point;

import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.util.Random;

import sprite.TetroRightS;
import sprite.Tetrobox;
import sprite.Tetromino;
import sprite.TetroSquare;
import sprite.TetroT;
import sprite.TetroStraight;
import sprite.TetroLeftS;

/**
 * @class  Board
 * 
 *   This is where the magic happens.  An extension of JPanel, 
 *   The board class directs the games flow of blocks, animations,
 *   holds all the pieces and talks to them all as well.
 *   
 * @author timondavis@yahoo.com
 */
public class Board extends JPanel implements Runnable {
  /**
   *  The tetrogrid is responsible for keeping track of all blocks on the board.
   *  
   *  The only exception to this is the tetromino, which is tracked independtly of the 
   *  tetrogrid
   */
   
   /******************************************************************
    *  
    *                SETUP, INITIALIZATION AND CONFIGURATION
    *
    ******************************************************************/
	private Tetrobox[][] tetrogrid;
	
	// The active tetromino peice.  Unlike other blocks, this bundle
	// can be manipulated by the user.
	private Tetromino activeTetro;
	
	// The width and height of the grid, measured by grid units
	private int gridWidth;
	private int gridHeight;
	
	// The width and height of a grid unit, measured in pixels
	private int unitWidth;
	private int unitHeight;
	
	// The thread which will handle animation
	private Thread animator;
	// The minimum # of milliseconds that must pass before each animation 
	// thread execution.
	private int DEFAULT_DELAY = 500;
	private int LEVEL_DELAY;
	private int DELAY;
	
	// The # of lines cleared recently.  Constantly refreshes when a new
	// tetromino is manufactured.
	private int linesCleared;
	
	// The total # of lines cleared in game.  Cumulative.
	private int totalLinesCleared;
	
	private GameFrame gameFrame;

	/**
	 * Basic Public Constructor for the Board class
	 */
	public Board() {
		
	  // Configure Panel
		setFocusable(true);
		setBackground(Color.black);
		setSize(400, 400);
		
		// Add custom inner-class key listener to Panel
		addKeyListener(new KeyboardInterface()); 
		
		// Configure board and measurement parameters
    gridWidth = 10;
	  gridHeight = 15;
    unitWidth = (int) 400 / gridWidth;
		unitHeight = (int) 400 / gridHeight;
		
		// Initialze the active Tetromino
		activeTetro = null;
		
		// Initialize the linesCleared counter
		linesCleared = 0;
		totalLinesCleared = 0;
		
		DELAY = LEVEL_DELAY = DEFAULT_DELAY;
		
		// Initialize the tetrogrid 
		initializeTetrogrid();
	}
	
	/**
	 * Sets the parent game frame that contains this board.
	 * 
	 * Because the board contains all of the game intelligence,
	 * a condiuit is created to the parent frame to send signals
	 * that update other subframes.  (A change is score, for example,
	 * can be reported back to the parent game frame, 
	 * which will in turn update the 'score' panel with the new score
	 * 
	 * @param frame
	 * 
	 *   The parent frame, which encapsulates this game board
	 */
	public void registerParentFrame(GameFrame frame) {
	
	  this.gameFrame = frame;
	}
	
	/**********************************************************
	 * 
	 * PUBLIC RUNTIME LOGIC
	 *         
	 **********************************************************/

	/**
	 * This method, tho I know what it is for, I'm not sure exactly how it works.
	 * I need it to get the thread started and running properly, given that this class
	 * implements Runnable.  Probably a necessary evil just to get things going.  Meh
	 */
	public void addNotify() {
    super.addNotify();		
		animator = new Thread(this);
		animator.start();
	}
	
	/**
	 * This method is a big deal.  run() is called to actually run a given thread.
	 * We're going to use this thread as our timer, so when this thread is called
	 * it immeidately put speed regulation controls into place.
	 */
	@Override
	public void run() {
		
	  long beforeTime, timeDiff, sleep; // Variables used in mathematically guarnateeing
	                                    // that a minimum amount of time is met before 
	                                    // starting the next cycle
	
	  // Get the Before Time, used for comparisons
		beforeTime = System.currentTimeMillis();
		
		// Inifinite loop here....
		while (true) {
			
		  // Execute sprite movement and repaint.  Empty full lines.
			cycle();
			repaint();
			
			if (activeTetro == null) {
			
			  dropFullLines();
			}
			
			// Calculate time remaining before next cycle
			timeDiff = System.currentTimeMillis() - beforeTime;
			sleep = DELAY - timeDiff ;
			
			// Set a minimum check, then sleep the thread until remaining timer has been
			// reduced to 0
			if (sleep < 0) {
				
				sleep = 2;
			}
			
			try {
				
			  // Sleep the thread to run out the clock
			  Thread.sleep(sleep);	
			}
			catch(InterruptedException e) {
				System.out.println("Interrupted");
			}
			
			// reset the new base time
			beforeTime = System.currentTimeMillis();
		}
	}
	
	/***********************************************************
	 * 
	 * PUBLIC DISPLAY LOGIC
	 * 
	 ***********************************************************/
	
	/**
	 * Paint the graphics onto the panel
	 * 
	 * This implements (overrides?) the graphics panel
	 * painting method for the Panel.  It must always
	 * match this signature to be invoked
	 * 
	 * @param Graphics g
	 * 
	 *   This is the graphics object which is being used to draw the panel.
	 */
	public void paint(Graphics g) {
		
	  // Call paint on the parent class.  This must pretty much always
	  // be the first command in a paint method.
		super.paint(g);
		
		// If there is currently an active tetro in place...
		if (activeTetro != null) {
		
		  // ... Draw each square on the tetro!
		  g = drawActiveTetro(g);
		}
		
		// Go through the grid board (the actual game board) and 
		// draw all visible tiles.
    g = drawGameboard(g);
	    
		// This clears out artifacts that weren't part of the current cycle.
	  g.dispose();
	}
	
	/*************************************************************************
	 * 
	 *          INITIALIZATION HELPERS
	 *          
	 *************************************************************************/
	
	/**
	 * Initialize the tetrogrid variable 
	 * 
	 * Cleans the board and inserts empty tetrominio blocks into each 
	 * space on the grid.
	 */
	private void initializeTetrogrid() {
	
		tetrogrid = new Tetrobox[gridWidth][gridHeight];
	
		// Loop through each space on the grid and insert a new
		// tetrobox, setting its base coordinates
	  for (int tg_x = 0 ; tg_x < gridWidth ; tg_x++) {
	  
	    for (int tg_y = 0; tg_y < gridHeight ; tg_y++) {
	    
	      // Create a new tetrobox at the current location, 
	      // settings its base coordinates to match the current array address
	      tetrogrid[tg_x][tg_y] = new Tetrobox(tg_x, tg_y, unitWidth, unitHeight);
	    }
	  }
	}
	
	/************************************************************
	 * 
	 * MECHANICS OF MOVEMENT and GAME MANAGEMENT
	 * 
	 ************************************************************/
	 
	 /***********************************************************
	  * REGULAR GAME CYCLE
	  **********************************************************/
	
	/**
	 * Movement cycle logic, responsible for implementing the
	 * data side of movement
	 */
	private void cycle() {
		
	  // If the active tetro exists (is not null)
	  if (activeTetro != null) {
	  
	    advanceTetro();
	    
	    // If any component of the tetromino has landed atop another
	    // tetroblock, we'll stop movement and transfer the tetromino
	    // blocks over to the grid.
	    if (!activeTetro.isMoveDown()) {
	    
	      transferTetroToGrid();
	    }
	  }
	  
	  // If there is no active tetro, create a new one and drop it 
	  // at the top of the board
	  else {
		
		  placeNewTetro();
		}
	}	
	
 /**
  * Advance the tetro down one line, if possible.  If not possible,
  * mark tetro advancement as halted.
  */
  private void advanceTetro() {
	    
	  // Set values for specific attributes of the tetro
    int tetroY = activeTetro.getPointY();
    int tetroX = activeTetro.getPointX();
    int tetroHeight = activeTetro.getTetroHeight();
    boolean hasSpaceDown = false;
		
		// Test Has Space Down
  	if (tetroY + tetroHeight < gridHeight) {
		 
	    hasSpaceDown = true;
		}
  	
  	// Here, we're checking to see if there is anything underneath the tetro,
  	// If there is a blocking tetro underneath, we need to stop the movement
  	if (hasSpaceDown) {
  	
  	  // Scan through the tetroboxes on the grid one line below the bottom 
  	  // line of the teromino
  	  
  	  for (int at_y = activeTetro.getTetroHeight() - 1; at_y >= 0 ; at_y--) {
  	  
		    for (int at_x = 0 ; at_x < activeTetro.getTetroWidth() ; at_x++) {
		 
          if (activeTetro.getTetro(at_x, at_y).isVisible()) {
     
            if ( (tetroY + at_y + 1 < gridHeight) && tetrogrid[at_x + tetroX][at_y + tetroY + 1].isVisible() ) {
              hasSpaceDown = false; 
            }
          }
        }
      }
    }
    
    // If the tetro can be moved down, move that puppy down
	  if (activeTetro.isMoveDown()) {

	    if (hasSpaceDown) {
 
        // move the tetro down 1 grid point ...
        activeTetro.setPointY(tetroY + 1);
      }
 
      else {
   
        // ... then stop the downward movement
        activeTetro.stopMoveDown(); 
     
        // Notify the scoreboard that the tetro has dropped
        gameFrame.dropTetro();
      }
    }
  }
 
	/**
	 * Deactivate the current tetro and transfer it to the board grid
	 */
  private void transferTetroToGrid() {
  
    int tetroY = activeTetro.getPointY();
    int tetroX = activeTetro.getPointX();
  
    // Grab a mini-grid of active tetroboxes from the active tetro
		Tetrobox[][] activeTetroGrid = activeTetro.getTetros();
			    
		// Move through x and y cycle
		for (int at_y = 0 ; at_y < activeTetro.getTetroHeight() ; at_y++) {
		
		  for (int at_x = 0 ; at_x < activeTetro.getTetroWidth() ; at_x++) {
		  
		    // If the tetrobox is visible, transfer it to the game grid
		    if (activeTetroGrid[at_x][at_y].isVisible()) {
		    
		      int x = tetroX + at_x;
		      int y = tetroY + at_y;
		      tetrogrid[x][y] = activeTetroGrid[at_x][at_y];
		      
		      tetrogrid[x][y].setPointX(x);
		      tetrogrid[x][y].setPointY(y);
		    }
	    }
		}
		  
		// Clear out the active tetro
		activeTetro = null;
  }
  
 /****************************************************************
  * TETROMINO SPEED CONTROL
  ****************************************************************/
  
 /**
  * Accelerate the drop speed of the current tetromino
  */
  private void accelerateDropSpeed(){
	   
	  DELAY = 50;
	}
	   
 /**
	* Return the drop speed of the current tetromino to current base value
	*/
  private void normalizeDropSpeed() {
	   
	  DELAY = LEVEL_DELAY;
	}
	
	/*************************************************************
	 * GENERATE TETROMINOS
	 ************************************************************/
	 
  /**
   * Place a new Tetromino on the board
   */
   private void placeNewTetro() {
   
		  // Reset local lines cleared counter
		  linesCleared = 0;
			
			activeTetro = getRandomTetro();
			activeTetro.startMoveDown();
   }
	
	/**
	 * Generates a random tetromino
	 * 
	 * @return Tetromino
	 *   A new random tetromino
	 */
	private Tetromino getRandomTetro() {
	
	  Random rand = new Random(System.currentTimeMillis());
	  int randNum = rand.nextInt(5);
	  
	  switch (randNum) {
	  
	    case(0): {
	  
	      return new TetroSquare(7, 0, unitWidth, unitHeight);
	    }
	  
	    case(1): {
	  
	      return new TetroT(7, 0, unitWidth, unitHeight);
	    }
	    
	    case(2): {
	    
	      return new TetroStraight(7, 0, unitWidth, unitHeight);
	    }
	    
	    case(3): {
	    
	      return new TetroLeftS(7, 0, unitWidth, unitHeight);
	    }
	    
	    case(4): {
	    
	      return new TetroRightS(7, 0, unitWidth, unitHeight);
	    }
	  }
	  
	  return null;
	}
	
	/***************************************************************
	 * SCAN GRID to CLEAR FULL LINES / DROP HIGHER LINES DOWN
	 ***************************************************************/
	 
	/**
	 * Tests for full rows and drops all above lines down to fill in.
	 */
	private void dropFullLines() {
	
	  while (scanFullBoard() != -1) {
	  
      int y = scanFullBoard();
	    
	    // Drop the lines down
	    clearLine(y);
	    dropLines(y);
	    
	    // Increment the line counters
	    linesCleared++;
	    totalLinesCleared++;
	  }
	  
	  // Report to the scoreboard
	  if (linesCleared > 0) {
	  
	    gameFrame.clearLines(linesCleared);
	  }
	}
	
	//
	// Scanning Logic 
	//
	
  /**	
	 * Reads the entire board.  Reports back with lowest
	 * full line #, or, -1 otherwise
	 */
	private int scanFullBoard() {
	
	  for (int y = gridHeight - 1; y >= 0 ; y--) {
	  
	    if (scanFullLine(y)) {
	    
	      return y;
	    }
	  }
	  
	  return -1;
	}
	
	/**
	 * Reads the indicated line to check if it is full
	 * 
	 * @param y
	 *   The line # to scan on the grid
	 *   
	 * @return boolean
	 *   Returns true if line if full, false if not
	 */
	private boolean scanFullLine(int y) {
	
	  boolean isFull = true;
	  
	  // Scan the row for invisible tetroboxes
		for (int x = 0 ; x < gridWidth; x++) {
			  
		  if (tetrogrid[x][y].isVisible() == false) {
			    
			  isFull =  false;
		  }
  	}
		
		return isFull;
	}
	
	//
	// Line Clearing Logic
	//
	
	/**
	 * Clears the indicated line of tetroblocks
	 * 
	 * @param y
	 *   The line from which the blocks will be cleared
	 */
	private void clearLine(int y) {
	
	  for (int x = 0 ; x < gridWidth ; x++) {
	  
	    tetrogrid[x][y].setVisible(false);
	  }
	}
	
	//
	// Line Dropping Logic
	//
	
	/**
	 * Drop all lines above the line indicated
	 * 
	 * @param y
	 * 
	 *   The base line above which all lines will be dropped down
	 */
	private void dropLines(int y) {
 
	  for (int y_progress = y ; y_progress >= 0 ; y_progress--) {
	  
	    dropLine(y_progress);
	  }
	}
	
	/**
	 * Drop a line of tetroblocks down by one row
	 * 
	 * @param y
	 *   The line to drop down
	 */
	private void dropLine(int y) {
	
	  if (y > 0) {
	  
      for (int x = 0 ; x < gridWidth ; x++) {
        
	      // Pull down the block above
        tetrogrid[x][y] = new Tetrobox(tetrogrid[x][y - 1]);
           
        tetrogrid[x][y].setPointX(x);
        tetrogrid[x][y].setPointY(y);
        tetrogrid[x][y].setWidth(unitWidth);
        tetrogrid[x][y].setHeight(unitHeight);
      } 
	  }
	  
	  else {
	  
	    for (int x = 0 ; x < gridWidth ; x++) {
	    
	      tetrogrid[x][y] = new Tetrobox(x, y, unitWidth, unitHeight, false);
	    }
	  }
	}
	
	/*****************************************************
	 * 
	 * USER INPUT RESPONSE
	 * 
	 ****************************************************/
	
  /****************************************************
	 * Left / Right Tetro Movement
   ***************************************************/	
	
	/**
	 *  Handles horizontal movement reacting to a keypress
	 *  
	 *  @param KeyEvent e
	 *  
	 *    Contains data about which key was pressed
	 */
  private void doHorizontalMovement(KeyEvent e) {

 	  int tetroY = -1;                 // Y coord of tetro on grid
    int tetroX = -1;                 // X coord of tetro on grid
    int tetroHeight = -1;            // The # of visible rows on the tetro
    int tetroWidth = -1;             // The # of visible cols on the tetro

    // Assign tetro attributes if there is an active tetromino on the board
    if (activeTetro != null) {
    
		  tetroY = activeTetro.getPointY();
      tetroX = activeTetro.getPointX();
      tetroHeight = activeTetro.getTetroHeight();
      tetroWidth  = activeTetro.getTetroWidth();
    }	  
	   
	  // If the 'left' key was pressed...
	  if (e.getKeyCode() == KeyEvent.VK_LEFT) {
	   
	    // If the tetro is allowed to move left...
	    if (hasSpaceLeft(tetroX, tetroY, tetroWidth, tetroHeight)) {
	    
	      // Activate the tetro's move left routine and refresh the screen
        activeTetro.keyPressed(e);
        repaint();
	    }
	  }
	   
	  // If the 'right' key was pressed...
	  if (e.getKeyCode() == KeyEvent.VK_RIGHT) {
	    
	    // If the tetro is allowed to move right...
	    if (hasSpaceRight(tetroX, tetroY, tetroWidth, tetroHeight)) {
	     
	      // Activate the tetro's move right routine and refresh the screen
	      activeTetro.keyPressed(e);
	      repaint();
	    }
	  }
  }
  
  /**
   *  Determines if the tetromino can move left
   *  
   *  @param int tetroX
   *    The x coord of the topleft tetrobox, relative to the board grid
   *    
   *  @param int tetroY
   *    The y coord of the topleft tetrobox, relative to the board grid
   *    
   *  @param int tetroWidth
   *    The width, in *visible* grid units, of the tetromino
   *    
   *  @param int tetroHeight
   *    The height, in *visible* grid units, of the tetromino
   *  
   *  @return 
   *    TRUE if movement left is OK
   *    FALSE if movement left is NOT OK
   */
  private boolean hasSpaceLeft(int tetroX, int tetroY, int tetroWidth, int tetroHeight) {
  
    // If there's no active tetromino,there's no point in going on...
    if (activeTetro == null) {
    
      return false;
    }
  
    boolean hasSpaceLeft = false;
  
  	// Test whether or not there is room on the grid to move left,
  	// regardless of tetrominos
		if (tetroX - 1 >= 0) {
		    
		  hasSpaceLeft = true;
		}
		  
		// If there is space on the grid, we'll scan for tetrominos
		if (hasSpaceLeft) {
		  
		  // Scan rows of the visible tetromino, from bottom to top
		  for (int at_y = tetroHeight - 1; at_y >= 0 ; at_y--) {
		    
		    // If the scanline is outside of the bounds of the grid, 
		    // cancel the row scan
		    if ((tetroY + at_y) + 1 >= gridHeight) {
		    
		      continue;
		    }
		    
		    // Scan the columns of the tetromino grid, from right to left.
		    for (int at_x = tetroWidth - 1; at_x >= 0 ; at_x--) {
		      
		      // If the scan column is outside of the bounds of the board, 
		      // cancel the scan
		      if ((tetroX + at_x) - 1 < 0) {
		        
		        continue;
		      }
		        
		      // Scan the space on the tetrogrid to the left of the current block.
		      // Deny movement, assuming that the current tetrobox is visible
		      if (activeTetro.getTetro(at_x, at_y).isVisible()) {
		        
		        if (tetrogrid[tetroX + at_x - 1][tetroY + at_y].isVisible()) {
		          
		          return false;
		        }
		      }
		    }
		  }
		}	
		
		return hasSpaceLeft;
  }
  
  /**
   *  Determines if the tetromino can move right
   *  
   *  @param int tetroX
   *    The x coord of the topleft tetrobox, relative to the board grid
   *    
   *  @param int tetroY
   *    The y coord of the topleft tetrobox, relative to the board grid
   *    
   *  @param int tetroWidth
   *    The width, in *visible* grid units, of the tetromino
   *    
   *  @param int tetroHeight
   *    The height, in *visible* grid units, of the tetromino
   *  
   *  @return 
   *    TRUE if movement right is OK
   *    FALSE if movement right is NOT OK
   */
  private boolean hasSpaceRight(int tetroX, int tetroY, int tetroWidth, int tetroHeight) {
  
    // The tetro has to be valid before we even begin to talk turkey...
    if (activeTetro == null) {
    
      return false;
    }
    
    boolean hasSpaceRight = false;
  
    // Test Has Space Right on the grid, regardless of other tetrominos
	  if (tetroX + tetroWidth < gridWidth) {
    
      hasSpaceRight = true;
    }
    
    // We've determined that there is space for movement on the grid.
    // Now let's scan for other Tetrominos...
    if (hasSpaceRight) { 
    
      // Loop through the visible tetromino grid rows, bottom to top.
      for (int at_y = tetroHeight - 1 ; at_y >= 0  ; at_y--) {
 
        // Don't bother scanning for grid points below the bottom 
        // line of the grid (besides, this would throw an error)
        if (tetroY + at_y >= gridHeight) {
   
          continue;
        }
      
        // Now that we've established we're scanning a valid row on the board
        // we'll scan each block column on the tetromino row, from right to left.
        for (int at_x = tetroWidth - 1 ; at_x >= 0 ; at_x--) {
    
          // Skip the scan if we're talking about a column outside of the bounds
          // of the board grid
          if (tetroX + at_x + 1 >= gridWidth) {
      
            continue;
          }
        
          // Scan the block to the right of the current block in question.
          // If it is visible, we'll report that movement is not viable.
          if (activeTetro.getTetro(at_x, at_y).isVisible()) {
       
            if (tetrogrid[tetroX + at_x + 1][tetroY + at_y].isVisible()) {
         
              hasSpaceRight = false;
            }
          }
        }
      } 
    }
    
    return hasSpaceRight;
  }
  
  /****************************************
   * Rotation Movement
   ****************************************/ 
  
  /**
   * Executes a 90 degree rotation to the left or right
   * 
   * @param KeyEvent e
   * 
   *   Represents the key that was pressed to activate the event
   */
  private void doRotationMovement(KeyEvent e) {
  
 	  int tetroY = -1;                 // Y coord of tetro on grid
    int tetroX = -1;                 // X coord of tetro on grid
    int tetroHeight = -1;            // The # of visible rows on the tetro
    int tetroWidth = -1;             // The # of visible cols on the tetro

    // Assign tetro attributes if there is an active tetromino on the board
    if (activeTetro != null) {
    
      // Get the relative xy point on the grid for tetromino
		  tetroY = activeTetro.getPointY();
      tetroX = activeTetro.getPointX();
      
      // Copy the tetromino into a test copy
      Tetromino testTetro = new Tetromino(activeTetro);
      
      testTetro.keyPressed(e);
      
      // Get the visible width and height of the rotated test tetro.
      tetroHeight = testTetro.getTetroHeight();
      tetroWidth  = testTetro.getTetroWidth();
      
      /**
       *  Bounds checking!  New visible tetro must live inside the grid.
       */
      // Reject rotations which move into out-of-bounds columns
      if (tetroX + tetroWidth > gridWidth) {
      
        return;
      }
      
      // Reject rotation which move into out-of-bounds rows
      if (tetroY + tetroHeight > gridHeight) {
      
        return;
      }
      
      /**
       * Tetromino overlap checking!  Tetroboxes cannot share the same space.
       */
      // Loop through columns of new rotated tetrogrid, left to right.
      for (int x = 0 ; x < tetroWidth ; x++) {
      
        // Loop through rows of new rotated tetrogrid, top to bottom.
        for (int y = 0 ; y < tetroHeight ; y++) {
        
          // If the current tetrobox is visible...
          if (testTetro.getTetro(x, y).isVisible()) {
          
            // Check to see if the tetrobox on the board grid is visible.
            // If it is, reject the rotation by returning flow of control
            // to caller.
            if (tetrogrid[x + tetroX][y + tetroY].isVisible()) {
           
              return; 
            }
          }
        }
      }
      
      // If we've come this far, congratulations!
      // you are cleared for rotation.  We'll send the 
      // keyboard command to the tetromino
      
      activeTetro.keyPressed(e);
      repaint();
    }	  
  }
  
  /************************************************************
   * 
   * GRAPHICS SUPPORT ROUTINES
   * 
   ************************************************************/
	
	/**
	 * Draw the active tetro on the graphics object
	 * 
	 * @param Graphics g
	 *   The graphics object on which to draw the tetro
	 *   
	 * @return Graphics
	 *   The adjusted graphics object
	 */
	 private Graphics drawActiveTetro(Graphics g) {
	 
		  // Loop through each tetrobox within the activeTetro 
		  for (int at_x = 0 ; at_x < activeTetro.WIDTH ; at_x++) {
		
		    for (int at_y = 0 ; at_y < activeTetro.HEIGHT ; at_y++) {
		    
		       if (at_x + activeTetro.getPointX() < gridWidth &&
		           at_y + activeTetro.getPointY() < gridHeight) {
		      
		         // Draw the tetrobox in the correct location on the pixelmap, 
		         // inferring its precise location using the grid cooridnates of the 
		         // tetromino relative to the grid, the tetrobox relative to the 
		         // tetromino.  Use the unitWidth and unitHeight variables to 
		         // plot out their desired size.
		         activeTetro.getTetro(at_x, at_y).draw(g, new Point (
		           (activeTetro.getTetro(at_x, at_y).getPointX() + activeTetro.getPointX()) * unitWidth, 
		           (activeTetro.getTetro(at_x, at_y).getPointY() + activeTetro.getPointY()) * unitHeight
		         ));
		      }
		    }
		  }
		  
		  return g;
	 }
	 
	 /**
	  * Draw the gameboard
	  * 
	  * @param Graphics g
	  *   The graphics object on which to draw the gameboard
	  *   
	  * @return Graphics
	  *   The adjusted graphics object
	  */
  private Graphics drawGameboard(Graphics g) {
	  
   	for (int i = 0 ; i < gridWidth ; i++) {
	  	for (int j = 0 ; j < gridHeight ; j++) {
		  		
		   	tetrogrid[i][j].draw(g, new Point(
				  tetrogrid[i][j].getPointX() * unitWidth,
				  tetrogrid[i][j].getPointY() * unitHeight
	  		)); 
			}
		} 
		  
	  return g;
	}
	  
	/**
	 * @class
	 * 
	 * @author timondavis@yahoo.com
	 * 
	 * This class is responsible for accepting keyboard input and taking appropriate action
	 * (which often means pipelining the keyEvent to objects who care...)
	 */
	public class KeyboardInterface extends KeyAdapter {
		
	  /**
	   * Overrides keyPressed method of the key adapter class
	   */
	  @Override
		public void keyPressed(KeyEvent e) {
		  
		  // If there is an active tetro...
		  if (activeTetro != null) {
		    
		    // If the user opted to move left or right...
		    if (e.getKeyCode() == KeyEvent.VK_LEFT || 
		        e.getKeyCode() == KeyEvent.VK_RIGHT) {
		          
          // React to horizontal movement code;
          doHorizontalMovement(e);
		    }
		      
		    // If up was pressed...
        if (e.getKeyCode() == KeyEvent.VK_UP) {
		      
		      // React with rotation code
		      doRotationMovement(e);
	      }
	      
		    if (e.getKeyCode() == KeyEvent.VK_DOWN) {
		    
		      accelerateDropSpeed();
		    }
		  }
	  }
	  
	  public void keyReleased(KeyEvent e) {
	  
	    if (e.getKeyCode() == KeyEvent.VK_DOWN) {
	    
	      normalizeDropSpeed();
	    }
	  }
	}
}